#使用蓝图装饰视图函数
from flask import g, redirect, render_template, abort, request, jsonify, current_app
from flask_sqlalchemy import Pagination

from info import db
from info.constants import QINIU_DOMIN_PREFIX, USER_COLLECTION_MAX_NEWS
from info.models import User, Category, News, tb_user_collection
from info.modules.user import user_blu
from info.utils.common import user_login_data
from info.utils.img_storage import upload_img
from info.utils.response_code import RET, error_map

"""显示个人中心"""
@user_blu.route('/user_info')
@user_login_data
def user_info():
    #判断用户是否登录
    user = g.user
    if not user:
        #如果没有登录就重定向到根路由
        return redirect("/")

    #渲染个人中心模板
    return render_template("news/user.html",user=user.to_dict())

"""个人中心外链显示及修改"""
@user_blu.route('/base_info',methods=['GET','POST'])
@user_login_data
def base_info():

    #思路
    #因为基本资料是外链，所以需要专门的视图函数来实现路由
    #修改基本资料也可以使用同一个接口，将请求方式增加一个post就可以了
    #get请求的时候是显示个人资料，post请求时是上传修改的数据
    #判断请求方式，如果是get请求就渲染个人资料模板
    #如果是post请求就先获取参数，个性签名，用户昵称，用户性别
    #校验参数
    #将数据库中的个人信息替换
    #返回json结果

    #判断用户是否登录
    user = g.user   #type: User
    if not user:
        #没有登录肯定进不了个人中心，所以只要报个错就行了
        return abort(404)


    # 判断请求方式，如果是get请求就渲染个人资料模板
    if request.method == 'GET':
        return render_template("news/user_base_info.html",user=user.to_dict())
    # 如果是post请求就先获取参数，个性签名，用户昵称，用户性别
    signature = request.json.get("signature")
    nick_name = request.json.get("nick_name")
    gender = request.json.get("gender")
    # 校验参数
    if not all([signature,nick_name,gender]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    if gender not in ["MAN","WOMAN"]:
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 将数据库中的个人信息替换,修改用户模型的数据
    user.signature = signature
    user.nick_name = nick_name
    user.gender = gender
    # 返回json结果
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])


"""头像显示及上传"""
@user_blu.route('/pic_info',methods=['POST','GET'])
@user_login_data
def pic_info():
    #思路
    #判断用户是否登录，如果没有登录就进了，给报个错
    #登录的情况下判断请求方式，如果是GET,那么就渲染头像的页面，并将用户模型传入
    # 如果是POST,那么就是修改头像，先获取参数，即读到用户上传的文件
    #再上传至七牛云（压缩方式优化，传输带宽高，数据去重
    #将文件的访问url返回给前端

    # 判断用户是否登录，如果没有登录就进了，给报个错
    user = g.user
    if not user:
        return abort(404)
    # 登录的情况下判断请求方式，如果是GET,那么就渲染头像的页面，并将用户模型传入
    if request.method == "GET":
        return render_template("news/user_pic_info.html",user=user.to_dict())
    # 如果是POST,那么就是修改头像，先获取参数，即读到用户上传的文件
    try:
        img_bytes = request.files.get("avatar").read()
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 再上传至七牛云（压缩方式优化，传输带宽高，数据去重
    try:
        #接收上传文件函数的访问上传的文件在七牛空间的url被处理成的key
        key = upload_img(img_bytes)

    except BaseException as e:
        #如果取的时候出错，则返回一个第三方插件错误
        current_app.logger.error(e)
        return jsonify(errno=RET.THIRDERR, errmsg=error_map[RET.THIRDERR])
    # 将文件的访问url返回给前端
    # data = {"user.avatar_url":QINIU_DOMIN_PREFIX + key}
    # 存进数据库
    user.avatar_url = key
    try:
        db.session.commit()
    except BaseException as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg="保存用户数据错误")

    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK],data = {"avatar_url":QINIU_DOMIN_PREFIX + key})


"""显示密码及修改密码"""
@user_blu.route('/pass_info',methods=['POST','GET'])
@user_login_data
def pass_info():
    #修改密码思路
    #判断用户是否登录
    #判断请求方式，如果是get,那么就是显示页面，渲染出密码页面
    #如果是post，那么就是修改密码
    #获取参数，旧密码和新密码
    #校验参数
    #校验旧密码是否正确
    #修改密码，将新密码存进数据库，通过封装好的加密密码
    #json返回结果


    # 判断用户是否登录
    user = g.user
    if not user:
        return abort(404)
    # 判断请求方式，如果是get,那么就是显示页面，渲染出密码页面
    if request.method == "GET":
        return render_template("news/user_pass_info.html",user=user.to_dict())
    # 如果是post，那么就是修改密码
    # 获取参数，旧密码和新密码
    old_password = request.json.get("old_password")
    new_password = request.json.get("new_password")
    # 校验参数
    if not all([old_password,new_password]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 校验旧密码是否正确,密码可以是字符串，所以不需要格式转换
    if not user.check_password(old_password):
        return jsonify(errno=RET.PWDERR, errmsg=error_map[RET.PWDERR])
    # 修改密码，将新密码存进数据库，通过封装好的加密密码
    user.password = new_password
    # json返回结果
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])


"""显示用户的收藏新闻"""
@user_blu.route('/collection')
@user_login_data
def collection():
    #新闻收藏思路
    #判断用户是否登录
    #获取参数，因为用户收藏的新闻是分页显示的，所以后台需要知道用户看到是第几页
    #获取当前页数
    #当前页数是整数，所以需要进行格式转换
    #根据用户模型对象查询出用户收藏的所有新闻进行分页返回pn对象
    #将查询出来的所有新闻列表和当前页和总页数封装成字典传给模板渲染

    # 判断用户是否登录
    user = g.user
    if not user:
        return abort(404)
    # 获取参数，因为用户收藏的新闻是分页显示的，所以后台需要知道用户看到是第几页
    # 请求不需要传敏感数据，所以用get拼接到URL上,当前页数默认为1
    # 获取当前页数
    page = request.args.get("p", 1)
    # 当前页数是整数，所以需要进行格式转换
    try:
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        #如果不能转那么就定义为第一页
        page = 1
    # 根据用户模型对象查询出用户收藏的所有新闻进行分页返回pn对象
    news_list = []
    total_pages = 1
    try:
        # pn = user.collection_news.paginate(page, USER_COLLECTION_MAX_NEWS)   #type: Pagination
        # #所有新闻列表转字典列表，用列表推倒式
        # news_list = [news.to_dict() for news in pn.items]
        # total_pages = pn.pages

        pn = user.collection_news.order_by(tb_user_collection.c.create_time.desc()).paginate(page,USER_COLLECTION_MAX_NEWS)

        for news in pn.items:
            news_dict = news.to_dict()
            #查询新闻收藏时间，
            from sqlalchemy import and_
            sql = and_(tb_user_collection.c.user_id == user.id,tb_user_collection.c.news_id == news.id)

            s = db.select([tb_user_collection.c.create_time]).where(sql)
            news_collect_time = db.engine.execute(s).fetchall()[0][0].strftime("%Y-%m-%d")
            news_dict["news_collect_time"] = news_collect_time
            news_list.append(news_dict)
    except BaseException as e:
        current_app.logger.error(e)
    # 将查询出来的所有新闻列表和当前页和总页数封装成字典传给模板渲染
    data = {
        "total_page":total_pages,
        "news_list":news_list,
        "cur_page":page
    }
    return render_template("news/user_collection.html",data=data)


"""发布新闻"""
@user_blu.route('/news_release',methods=['POST','GET'])
@user_login_data
def news_release():
    #判断用户是否登录
    #判断请求方式，如果是get，就渲染页面，需要将新闻表的分类传进去
    # 如果是post，那么就是发布新闻，上传数据
    #先获取数据，新闻的标题，分类id，摘要，索引图片，内容
    #校验参数
    #分类id的格式转换
    #上传发布的新闻至七牛云空间
    #创建新闻模型
    #保存新闻表的各个字段到数据库
    #添加以自动提交
    #返回json给前端

    # 判断用户是否登录
    user = g.user
    if not user:
        return abort(404)
    # 判断请求方式，如果是get，就渲染页面，需要将新闻表的分类传进去
    if request.method == 'GET':
        #查询分类表的所有分类，得到一个分类列表，先定义一个空列表
        categories = []
        try:
            categories = Category.query.all()  #type: list[Category]
        except BaseException as e:
            current_app.logger.error(e)
        #要将最新的那个分类id排除，就是id=1时
        if len(categories):
            categories.pop(0)

        return render_template("news/user_news_release.html",categories=categories)
    # 如果是post，那么就是发布新闻，上传数据
    # 先获取数据，新闻的标题，分类id，摘要，索引图片，内容,前端用的是form表单，data-form请求
    title = request.form.get("title")
    category_id = request.form.get("category_id")
    digest = request.form.get("digest")
    index_img_file = request.files.get("index_image")
    content = request.form.get("content")
    # 校验参数
    if not all([title,category_id,digest,index_img_file,content]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 分类id的格式转换
    try:
        category_id = int(category_id)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    # 上传发布的新闻至七牛云空间
    #先读取文件
    try:
        index_img_bytes = index_img_file.read()
        #存到七牛云然后获取到访问文件的urld key
        key = upload_img(index_img_bytes)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.THIRDERR, errmsg=error_map[RET.THIRDERR])
    # 创建新闻模型
    news = News()
    # 保存新闻表的各个字段到数据库
    news.title = title
    news.category_id = category_id
    news.digest = digest
    news.index_image_url = QINIU_DOMIN_PREFIX + key
    news.content = content
    #发布新闻的作者要存一下
    news.user_id = user.id
    #发布新闻的来源，可以是别人的，也可以是个人发布的
    news.source = "个人发布"
    #设置发布状态，审核未通过-1，审核中1，审核通过0
    #所以写到这里要考虑在新闻首页显示的新闻中，只能显示审核通过的，在那里要进行过滤
    news.status = 1
    # 添加以自动提交
    db.session.add(news)
    # 返回json给前端
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])


"""显示发布过的新闻列表"""
@user_blu.route('/news_list')
@user_login_data
def news_list():
    #在发布完新闻的时候会跳转到这个页面
    #因为发布的新闻列表也是分页的，是从数据库中查询出来再渲染，所以和收藏的新闻是一个套路，啊呸，一个思路
    #判断用户是否登录
    #get请求方式，所以用args获取参数，当前页数
    #格式转换
    #查询该用户发布的所有新闻列表
    #将总页数和所有发布的新闻列表和当前页数封装成字典
    #全局刷新，所以将封装好的字典传进模板进行渲染

    #判断用户是否登录
    user = g.user
    if not user:
        return abort(404)
    #get请求方式，所以用args获取参数，当前页数
    page = request.args.get("p", 1)
    #格式转换
    try:
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        #不能转就给默认值
        page = 1
    #查询该用户发布的所有新闻列表,新闻表中的作者是该用户,进行分页paginate(当前页数，一页显示的新闻数)
    news_list = []
    total_pages = 1
    try:
        pn = News.query.filter(News.user_id == user.id).paginate(page,USER_COLLECTION_MAX_NEWS)
        total_pages = pn.pages
        #将新闻模型对象列表转成字典，用列表推导式
        #因为新闻发布的时间在News表重视用to_review_dict()封装过的格式，所以调用这个
        news_list = [news.to_review_dict() for news in pn.items]
    except BaseException as e:
        current_app.logger.error(e)
    #将总页数和所有发布的新闻列表和当前页数封装成字典
    data = {
        "total_page":total_pages,
        "news_list":news_list,
        "cur_page":page
    }
    #全局刷新，所以将封装好的字典传进模板进行渲染
    return render_template("news/user_news_list.html",data=data)


"""显示关注列表"""
@user_blu.route('/user_follow')
@user_login_data
def user_follow():
    #思路
    #先判断用户是否登录，如果没有登录就进了个人中心，抛异常
    #登录的话先获取参数，当前所在页数即可
    #校验参数及格式转换
    #查询该用户关注的所有人列表
    #将总页数，所有被该用户关注的人模型对象列表，当前页数封装成字典
    #将封装的字典传入模板进行渲染

    # 先判断用户是否登录，如果没有登录就进了个人中心，抛异常
    user = g.user
    if not user:
        return abort(404)
    # 登录的话先获取参数，当前所在页数即可
    page = request.args.get("p", 1)
    # 校验参数及格式转换
    try:
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        #将当前页数设置为默认的1
        page = 1
    # 查询该用户关注的所有人列表
    user_list = []
    total_pages = 1
    try:
        #user.followed是用户关注的所有人的反向引用
        pn = user.followed.paginate(page,USER_COLLECTION_MAX_NEWS)
        user_list = [user.to_dict() for user in pn.items]
        total_pages = pn.pages
    except BaseException as e:
        current_app.logger.error(e)
    # 将总页数，所有被该用户关注的人模型对象列表，当前页数封装成字典
    data = {
        "total_page":total_pages,
        "user_list":user_list,
        "cur_page":page
    }
    # 将封装的字典传入模板进行渲染
    return render_template("news/user_follow.html",data=data)


"""显示用户个人主页"""
@user_blu.route('/other')
@user_login_data
def other():
    #思路
    #就是点进作者头像或昵称可以进入该用户的个人主页
    #先获取参数，想要进入某用户主页他的id
    #校验参数及格式转换
    #根据他的id查询出用户模型对象
    #判断该用户是否存在
    #判断用户是否登录，没有登录也可以看别人的个人主页
    #定义一个当前登录用户是否关注该作者该用户的标记，默认为False
    #如果用户登录，如果当前用户关注了该作者，就把是否关注改为True
    # 进入该用户的个人主页后，会显示该用户所有发过的新闻，也要用到分页显示
    #先获取参数，当前所在页数
    # 校验参数及格式转换
    #查询该用户发布的所有新闻
    #将总页数，所有新闻列表，当前页数封装成字典
    #将该作者模型和当前用户模型和是否关注标记和封装好的字典传进模板进行渲染


    # 就是点进作者头像或昵称可以进入该用户的个人主页
    # 先获取参数，想要进入某用户主页他的id
    user_id = request.args.get("user_id")
    # 校验参数及格式转换
    try:
        user_id = int(user_id)
    except BaseException as e:
        current_app.logger.error(e)
        return abort(404)
    # 根据他的id查询出用户模型对象
    try:
        author = User.query.get(user_id)
    except BaseException as e:
        current_app.logger.error(e)
        return abort(404)
    # 判断该用户是否存在
    if not author:
        return abort(404)
    # 判断用户是否登录，没有登录也可以看别人的个人主页
    user = g.user
    # 定义一个当前登录用户是否关注该作者该用户的标记，默认为False
    is_followed = False
    # 如果用户登录，如果当前用户关注了该作者，就把是否关注改为True
    if user:
        if author in user.followed:
            is_followed = True
    # 进入该用户的个人主页后，会显示该用户所有发过的新闻，也要用到分页显示
    # 先获取参数，当前所在页数
    page = request.args.get("p", 1)
    # 校验参数及格式转换
    try:
        page = int(page)
    except BaseException as e:
        current_app.logger.error(e)
        page = 1
    # 查询该用户发布的所有审核通过的新闻
    news_list = []
    total_pages = 1
    try:
        pn = News.query.filter(News.user_id == author.id,News.status == 0).paginate(page,USER_COLLECTION_MAX_NEWS)
        news_list = [news.to_review_dict() for news in pn.items]
        total_pages = pn.pages
    except BaseException as e:
        current_app.logger.error(e)
    # 将总页数，所有新闻列表，当前页数封装成字典
    data = {
        "total_page":total_pages,
        "news_list":news_list,
        "cur_page":page
    }
    user = user.to_dict() if user else None
    # 将该作者模型和当前用户模型和是否关注标记和封装好的字典传进模板进行渲染
    return render_template("news/other.html",author=author,user=user,is_followed=is_followed,data=data)